package org.grill.clovercash.ui;

import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.bindings.keys.ParseException;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalListener;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.jface.fieldassist.SimpleContentProposalProvider;
import org.eclipse.jface.fieldassist.TextContentAdapter;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
import org.grill.clovercash.CloverCashUIPlugin;
import org.grill.clovercash.ReconcileView;
import org.grill.clovercash.SplitRowOwner;
import org.grill.clovercash.accounts.Account;
import org.grill.clovercash.accounts.Split;
import org.grill.clovercash.accounts.Transaction;
import org.grill.clovercash.storage.StatementEntry;
import org.joda.time.LocalDate;

@SuppressWarnings("unused")
public class SplitRowView extends Composite {
	private Account account;
	private Split split;
	private SplitRowOwner view;
	private boolean isNew = false;
	private LocalDate date;

	public SplitRowView(Composite parent, SplitRowOwner view, Account account, Split split) {
		super(parent, SWT.NONE);
		
		this.account = account;
		this.split = split;
		this.view = view;

		
		build();
		
		if (split == null)
			isNew = true;
		
		buildWidgets();
		fillData();
	}

	private void build() {
		GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
//		gridData.widthHint = 300;
		setLayoutData(gridData);
		
		GridLayout layout = new GridLayout(7 + (view.showBothAccounts() ? 1 : 0), false);
		layout.verticalSpacing = 0;
		layout.marginHeight = 0;
		setLayout(layout);
	}
	
	public SplitRowView(Composite parent, SplitRowOwner view, Account account, LocalDate date) {
		super(parent, SWT.NONE);
		this.account = account;
		this.view = view;
		this.date = date;
		isNew = true;
		
		build();
		
		buildWidgets();
		fillData();
	}

	private static NumberFormat format = NumberFormat.getInstance();
	static {
		format.setMinimumFractionDigits(2);
		format.setMaximumFractionDigits(2);
		format.setGroupingUsed(false);
	}
	
	public void setTotal(BigDecimal total) {
		fillData();
		totalLabel.setText(NumberFormat.getCurrencyInstance().format(total));
	}

	public void fillData() {
		if (split != null) {
			dateText.setText(split.trans.date.toString("MM-dd-YYYY"));
			descText.setText(split.trans.description);
			disableAdapter(otherAdapter);
			otherText.setText(split.trans.getOther(split).owner.getDisplayName());

			if (split.value.floatValue() > 0) {
				debitText.setText(format.format(split.value));
			} else {
				creditText.setText(format.format(split.value.negate()));
			}
			
			if (split.reconciled) {
				Font bannerFont = JFaceResources.getBannerFont();
				dateText.setFont(bannerFont);
				descText.setFont(bannerFont);
				otherText.setFont(bannerFont);
				debitText.setFont(bannerFont);
				creditText.setFont(bannerFont);
			}
		} else {
			System.out.println("Filling data for new row");
			dateText.setText(date.toString("MM-dd-YYYY"));
			descText.setText("");
			disableAdapter(otherAdapter);
			otherText.setText("");
			debitText.setText("");
			if (accountText != null) {
				disableAdapter(accountAdapter);
				accountText.setText("");
			}
			creditText.setText("");
//			if (enabled)
//				dateText.setText(new LocalDate().toString("MM-dd-YYYY"));
		}
		modified  = false;
		fillComboPossibilities();
	}

	String[] comboPossibilities;
	
	
	HashMap<String, Account> accountMap;
	
	private void fillComboPossibilities() {
//		otherCombo.removeAll();
		List<Account> accounts = CloverCashUIPlugin.getDefault().getRootAccount().listAllChildren();
		if (account != null && !view.showBothAccounts())
			accounts.remove(account);
//		for (Account a : accounts) {
//			otherCombo.add(a.getDisplayName());
//		}
		
		accountMap = new HashMap<String, Account>();
		
		comboPossibilities = new String[accounts.size()];
		for (int i = 0; i < accounts.size(); i++) {
			Account account2 = accounts.get(i);
			String displayName = account2.getDisplayName();
			comboPossibilities[i] = displayName;
			accountMap.put(displayName, account2);
			
		}
		new SimpleContentProposalProvider(comboPossibilities);
//		adapter.setContentProposalProvider(new SimpleContentProposalProvider(comboPossibilities));
	}

	private Text dateText;
	private Text descText;
	private Text accountText;
	private Text otherText;
	private Text debitText;
	private Text creditText;
	private Label totalLabel;
	private Button okButton;
	
	private void buildWidgets() {
		dateText = new Text(this, SWT.LEFT | SWT.BORDER);
		dateText.setLayoutData(new GridData(100, -1));
		
		descText = new Text(this, SWT.LEFT | SWT.BORDER);
		descText.setLayoutData(new GridData(150, -1));
		
		if (view.showBothAccounts()) {
			accountText = new Text(this, SWT.LEFT | SWT.BORDER);
			GridData gridData = new GridData(250, -1);
			accountText.setLayoutData(gridData);
		}
		
		otherText = new Text(this, SWT.LEFT | SWT.BORDER);
		GridData gridData = new GridData(250, -1);
		otherText.setLayoutData(gridData);
		
		debitText = new Text(this, SWT.RIGHT | SWT.BORDER);
		debitText.setLayoutData(new GridData(75, -1));
		
		creditText = new Text(this, SWT.RIGHT | SWT.BORDER);
		creditText.setLayoutData(new GridData(75, -1));
		
		totalLabel = new Label(this, SWT.RIGHT);
		totalLabel.setLayoutData(new GridData(85, -1));
		
		okButton = new Button(this, SWT.PUSH);
		okButton.setText("OK");
		okButton.addListener(SWT.MouseUp, new Listener() {
			public void handleEvent(Event arg0) {
				view.saveRow();
			}
		});
		
		addListeners();
		
		if (accountText != null)
			accountAdapter = createAdapter(accountText, otherText);
		otherAdapter = createAdapter(otherText, debitText);
		
//		adapter.setFilterStyle(ContentPropAdapter.FILTER_CUMULATIVE);
		
		disableWidgets();
////		 Add a key listener
//		otherCombo.addKeyListener(new KeyAdapter() {
//		 public void keyReleased(KeyEvent keyEvent) {
//			Combo cmb = ((Combo)keyEvent.getSource());
//			setClosestMatch(cmb);
//		 }
//					
//		 // Move the highlight back by one character for backspace
//		 public void keyPressed(KeyEvent keyEvent) {
//			if (keyEvent.keyCode == SWT.BS)
//			{
//		 	  Combo cmb = ((Combo)keyEvent.getSource());
//			  Point pt = cmb.getSelection();
//			  cmb.setSelection(new Point(Math.max(0, pt.x - 1), pt.y));
//			}
//		 }
//		});
	}

	private ContentPropAdapter createAdapter(final Text myText, final Control nextControl) {
		ContentPropAdapter adapter;
		try {
			KeyStroke keyStroke = KeyStroke.getInstance("Alt+Space");
			adapter = new ContentPropAdapter(myText, new TextContentAdapter(), new IContentProposalProvider() {
				public IContentProposal[] getProposals(String contents, int position) {
//					System.out.println("Getting proposals... " + contents);
					ArrayList<IContentProposal> proposals = new ArrayList<IContentProposal>();
					String skipString = null;
					if (myText == otherText && accountText != null)
					{
						String selectedName = accountText.getText();
						if (accountMap.get(selectedName) != null)
							skipString = selectedName;
					}
					for (String s : comboPossibilities) {
						if (s.equals(skipString)) 
							continue;
						final String poss = s.toLowerCase();
						final String prop = contents.toLowerCase();
						if (poss.indexOf(prop) >= 0) {
//							System.out.println("Proposing " + s);
							final String tmp = s;
							proposals.add(new IContentProposal() {
								public String getContent() {
									return tmp;
								}
								public String getDescription() {
									return null;
								}
								public String getLabel() {
									return null;
								}
								public int getCursorPosition() {
									return poss.length();
								}
							});
						}
					}
					
					if (skipString != null) {
						final String accountName = account.getDisplayName();
						Collections.sort(proposals, new Comparator<IContentProposal>() {
							public int compare(IContentProposal o1, IContentProposal o2) {
								boolean firstHas = o1.getContent().startsWith(accountName);
								boolean secondHas = o2.getContent().startsWith(accountName);
								if ((firstHas && secondHas)
										|| (!firstHas && !secondHas))
									return o1.getContent().compareTo(o2.getContent());

								if (firstHas)
									return 1;
								else return -1;
							}
						});
					}
					
					return proposals.toArray(new IContentProposal[0]);
				}
			}, null, null);
			adapter.setAutoActivationDelay(50);

		} catch (ParseException e) {
			return null;
		}
		
		myText.getShell().addFocusListener(new FocusAdapter() {
			@Override
			public void focusLost(FocusEvent event) {
				if ((event.display.getFocusControl().getShell().getStyle() & SWT.ON_TOP) != 0) {
					myText.setText("");
				} 
			}
		});
		
		adapter.setPropagateKeys(true);
		adapter.setProposalAcceptanceStyle(ContentPropAdapter.PROPOSAL_REPLACE);
		final ContentPropAdapter adapterCopy = adapter;
		adapter.addContentProposalListener(new IContentProposalListener() {
			public void proposalAccepted(IContentProposal proposal) {
				System.out.println("Accepted " + proposal.getContent());
				adapterCopy.setEnabled(false);
				
				getDisplay().timerExec(500, new Runnable() {
					public void run() {
						adapterCopy.setEnabled(enabled);
					}
				});
				
				nextControl.setFocus();
			}
		});
		return adapter;
	}
	
	private void disableAdapter(final ContentPropAdapter adapter) {
		adapter.setEnabled(false);
		
		getDisplay().timerExec(500, new Runnable() {
			public void run() {
				adapter.setEnabled(enabled);
			}
		});
	}

//
//	private void setClosestMatch(Combo combo) {
//		String str = combo.getText();
//		str = str.toUpperCase();
//		String[] cItems = combo.getItems();
//
//		int index = 0;
//		
//		int charsMatched = 0;
//		
//		for (String s : cItems) {
//			
//		}
//		
////		Get Selection has to be called before, because combo.select causes all //indexes to be reset
//		Point pt = combo.getSelection();
//
//		combo.select(index);
//		combo.setText(cItems[index]);
//		combo.setSelection(new Point(pt.x, cItems[index].length()));
//	}
//	
	private void addListeners() {
		addMouseListener(new MouseAdapter() {
			@Override
			public void mouseUp(MouseEvent arg0) {
				if (!enabled) {
					if (split != null && split.reconciled) {
						if (!MessageDialog.openConfirm(getShell(), "Really edit reconciled?", "Editing a reconciled transaction is probably a really bad idea.\n\nContinue?"))
							return;
					}
					if (view.shouldActivate(SplitRowView.this)) {
						enableWidgets(true);
						getDisplay().getCursorControl().setFocus();
					}
				}
			}
		});
		
		for (Control c : getChildren()) {
			if (c instanceof Text) {
				c.addListener(SWT.Modify, new Listener() {
					public void handleEvent(Event arg0) {
						modified = true;
					}
				});
				if (c != otherText && c != accountText)
					c.addKeyListener(new KeyAdapter() {
						public void keyPressed(KeyEvent arg0) {
							if (arg0.keyCode == SWT.CR || arg0.keyCode == SWT.LF) {
								view.saveRow();
							} else if (arg0.keyCode == SWT.ESC) 
								view.cancelRowChange();
						}
					});
			}
		}
	}

	public void disableWidgets() {
		enableWidgets(false);
	}
	
	private boolean enabled = false;
	private boolean modified;
	private ContentPropAdapter otherAdapter;
	private ContentPropAdapter accountAdapter;

	public void enableWidgets(boolean b) {
		if (isDisposed())
			return;
		
		dateText.setEnabled(b);
		descText.setEnabled(b);
		otherText.setEnabled(b);
		debitText.setEnabled(b);
		if (accountText != null) {
			accountText.setEnabled(b);
			accountAdapter.setEnabled(b);
		}
		creditText.setEnabled(b);
		otherAdapter.setEnabled(b);
		okButton.setVisible(b);
		enabled = b;

	}

	DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy");

	public boolean validate() {
		try {
			dateFormat.parse(dateText.getText());
		} catch (Exception e) {
			e.printStackTrace();
			complainAbout("Invalid Date");
			return false;
		}
		if (descText.getText().length() == 0) {
			complainAbout("No description entered");
			return false;
		}
		
		if (accountText != null) {
			if (accountMap.get(accountText.getText()) == null) {
				complainAbout("Invalid source account specified");
				return false;
			}
		}
		
		if (accountMap.get(otherText.getText()) == null) {
			complainAbout("Invalid destination account specified");
			return false;
		}

		if (accountText != null && accountText.getText().equals(otherText.getText())) {
			complainAbout("You must have a different account for source and destination.");
			return false;
		}
		
		String debit = debitText.getText();
		String credit = creditText.getText();
		if (debit.length() == 0 && credit.length() == 0) {
			complainAbout("Please enter a debit or credit");
			return false;
		}
		
		if (debit.length() != 0 && credit.length() != 0) {
			complainAbout("Please enter only a debit or credit, but not both");
			return false;
		}
		
		if (debit.length() != 0) {
			try {
				Double.parseDouble(debit);
			} catch (NumberFormatException e) {
				complainAbout("Invalid number for debit");
				return false;
			}
		} else {
			try {
				Double.parseDouble(credit);
			} catch (NumberFormatException e) {
				complainAbout("Invalid number for credit");
				return false;
			}
		}
			
		
		return true;
	}

	private void complainAbout(String string) {
		MessageDialog.openError(getShell(), "Validation Error", string);
	}

	public boolean isNew() {
		return isNew;
	}
	
	public void setNew(boolean isnew) {
		this.isNew = isnew;
	}

	public boolean isModified() {
		return modified;
	}
	
	public void setModified(boolean modified) {
		this.modified = modified;
	}

	public Split getSplit() {
		return split;
	}
	
	public String getDescription() {
		return descText.getText();
	}
	
	public Account getOtherAccount() {
		return accountMap.get(otherText.getText());
	}
	
	public Account getAccount() {
		return accountMap.get(accountText.getText());
	}
	
	public BigDecimal getValue() {
		if (debitText.getText().length() > 0) {
			return new BigDecimal(debitText.getText());
		}
		return new BigDecimal(creditText.getText()).negate();
	}
	
	public LocalDate getDate() {
		try {
			return new LocalDate(dateFormat.parse(dateText.getText()));
		} catch (java.text.ParseException e) {
			e.printStackTrace();
			return null;
		}
	}

	public void setSplit(Split split) {
		this.split = split;
	}

	public void setDescActive() {
		descText.setFocus();
	}

	public void setDate(LocalDate date) {
		this.date = date;
	}


	public void fillAndActivate(StatementEntry entry) {
		date = entry.date;
		fillData();
		if (entry.value.floatValue() > 0)
			debitText.setText(format.format(entry.value));
		else creditText.setText(format.format(entry.value.negate()));
		String desc = entry.payeeName;
		if (desc.length() == 0)
			desc = entry.memo;
		descText.setText(desc);
		String displayName = account.getDisplayName();
		accountText.setText(displayName);
		enableWidgets(true);
		accountText.setFocus();
	}
}
